home *** CD-ROM | disk | FTP | other *** search
/ AppleScript - The Beta Release / AppleScript - The Beta Release.iso / Documentation / User Documentation / AppleScript Lang Prog Notes / Object-Oriented Prog in AS < prev   
Encoding:
Text File  |  1992-11-23  |  13.9 KB  |  459 lines  |  [ttro/ttxt]

  1. Object-oriented programming in AppleScript
  2. ==========================================
  3. Warren Harris    11/22/92
  4.  
  5. Intro
  6. -----
  7. You've heard John Scully talk about it, now you can experience it for real:
  8. AppleScript is an OODL (Object-Oriented Dynamic Language) with which you and
  9. your friends can perform true object-oriented programming.
  10.  
  11. What do we mean by object-oriented programming?  We mean that AppleScript has
  12. constructs for bundling handlers and properties together, and a means by which
  13. messages can be sent to these bundles.  We call these bundles "actors."  Actors
  14. are used as both classes and instances in AppleScript.  Actors can be sent
  15. messages by using the "tell" statement.  
  16.  
  17. Here's a quick example demonstrating how actors can be constructed, and how
  18. they can respond differently to the same message.  Let's define two actors,
  19. John and Jean Luis who respond to the SayHello message differently:
  20.  
  21.     actor "John"
  22.         to SayHello to someone
  23.             return "Hello " & someone
  24.         end SayHello
  25.     end actor
  26.     
  27.     actor "Jean Luis"
  28.         to SayHello to someone
  29.             return "Bonjour " & someone
  30.         end SayHello
  31.     end actor
  32.  
  33. This defines John and Jean Luis.  The actor...end actor construct brackets the 
  34. commands that constitute the definition of each actor.  Each of them has a 
  35. single handler definition, SayHello, which takes a parameter describing who
  36. to greet.  Since SayHello is a message name we've made up (rather than one
  37. which comes from some application's terminology), so we're not allowed to have
  38. any spaces or special characters in the name.  Now if we tell each of them to
  39. say hello, we see different results returned:
  40.  
  41.     tell actor "John" to SayHello to "Michael"
  42.     --> "Hello Michael"
  43.     
  44.     tell actor "Jean Luis" to SayHello to "Michael"
  45.     --> "Bonjour Michael"
  46.  
  47.  
  48. Properties
  49. ----------
  50. Actors can also contain properties.  Just as we define properties at the top
  51. level of a Toy Surprise file, we can define properties inside of actors.  These
  52. properties have values which persist after the actor is created, and are
  53. accessible whenever the actor is sent a message.
  54.  
  55. For example, we can add a counter to one of the above actors as a property:
  56.  
  57.     actor "John"
  58.         property n : 0
  59.         
  60.         to SayHello to someone
  61.             set n to n + 1
  62.             return "Hello " & someone & "(I've told you " & n & " times)"
  63.         end SayHello
  64.     end actor
  65.  
  66. Now when we send the SayHello message to John, he becomes indignant, complaining
  67. about the number of times he's had to tell you:
  68.  
  69.     tell actor "John" to SayHello to "Michael"
  70.     --> "Hello Michael (I've told you 1 times)"
  71.     
  72.     tell actor "John" to SayHello to "Michael"
  73.     --> "Hello Michael (I've told you 2 times)"
  74.     
  75.     tell actor "John" to SayHello to "Michael"
  76.     --> "Hello Michael (I've told you 3 times)"
  77.  
  78. Note however that if you do this in Toy Surprise, each time you reevaluate the 
  79. actor "John" construct it re-initializes the counter n to 0.  Therefore if you
  80. put one tell statement at the bottom every time you run it it will say "1 time"
  81. whereas if you put two tell statements at the bottom it will always say "2
  82. times", etc.
  83.  
  84.  
  85. A note about Toy Surprise
  86. -------------------------
  87. Toy Surprise operates by constructing an actor out of the script statements you
  88. type into each of its windows.  It's as if you implicitly added the actor...
  89. end actor construct to the handlers and commands in the script.  This means that
  90. once a script is compiled its property declarations are persistent.  Just for
  91. fun, try the following script in a Toy Surprise window:
  92.  
  93.     property p : 0
  94.     set p to p+1
  95.     p
  96.  
  97. When you press the Run button, you'll see what you expect, the result 1.
  98. However, if you press the Run button again (without touching the script text
  99. or causing a recompile) you'll get the result 2!  Doing this again yields 3!
  100. What's going on?
  101.  
  102. Each time you run this script, it increments the persistent property p.  P is
  103. only initialized to 0 when the script is compiled.  After that it retains its
  104. value from the last invocation.
  105.  
  106. This is great for applets and droplets.  They essentially can remember things
  107. from invocation to invocation.  Try this variation as an applet:
  108.  
  109.     property p : 0
  110.     set p to p+1
  111.     display dialog p as string
  112.  
  113. (Sorry the "as string" is required for display dialog).  Each time you run this
  114. applet it displays the incremented value of p.  When it quits it writes itself
  115. out remembering the last value of p.
  116.  
  117. Droplets currently have a bug -- they're supposed to stay up after you run them,
  118. listening for new AppleEvents to respond to.  The behavior ends up being the 
  119. same as for applets, but you don't have to put up with the overhead of saving
  120. things to disk when the process quits, and the startup time the next time it's
  121. invoked.  When you finally send a droplet the Quit message (or switch to it's
  122. layer and hit Command-Q) it then saves its properties.
  123.  
  124. With droplets you should be able to write something like:
  125.  
  126.     property p : 0
  127.     on close
  128.         set p to p + 1
  129.         display dialog p as string
  130.     end
  131.  
  132. This isn't normally what you do with the Close message, but this is just for 
  133. demonstration purposes.  Then, if the droplet were saved with the name "doit"
  134. and from another script we ran:
  135.  
  136.     tell application "doit"
  137.         close
  138.         close
  139.     end
  140.  
  141. The first Close message would cause the dialog containing the number 1 to be
  142. displayed, and the second Close message would cause the number 2 to be 
  143. displayed.  If you ran this again, 3 and 4 would come up in the dialog box.
  144. (Remember, don't try this at home.  It's broken now.  We just wanted to let you
  145. know where we're going with all this.)
  146.  
  147. The bottom line:  actors, applets and droplets are all "objects" in the object-
  148. oriented programming sense.  You can send them messages whether they're 
  149. in their own application layer (applets and droplets) or whether they're just
  150. objects in the current program.
  151.  
  152.  
  153. Classes and Instances
  154. ---------------------
  155. Real object-oriented programming is more than just sending messages though.
  156. Essential to OOP is the notion of classes and instances, and inheritance. 
  157. AppleScript supports both of these concepts.  We'll look at classes and 
  158. instances first.
  159.  
  160. AppleScript doesn't really make a distinction between the things that are
  161. classes and the things that are instances at runtime -- they're all actors.
  162. The distinction lies in how actors are used.  There are two ways of using
  163. actors that make them behave like classes: by naming actors, and by writing
  164. constructor functions.  All other uses of actors are to treat them as instances
  165. -- getting and setting properties (call them slots, instance variables,
  166. whatever) and sending them messages.
  167.  
  168. Actors may be named or unnamed.  Named actors can be used like classes because
  169. there is a unique actor associated with a given actor name.  This gives a 
  170. convenient way for the actor to be copied in order to construct new instances.
  171. For example, if we define a Point actor to have an x coordinate of 50, and
  172. later define it to have an x coordinate of 0, the Point definition is updated:
  173.  
  174.     actor "Point"
  175.         prop x : 50
  176.         prop y : 0
  177.     end
  178.     
  179.     set myPoint to actor "Point"
  180.     
  181.     x of myPoint 
  182.     --> 50
  183.     
  184.     actor "Point"
  185.         prop x : 0
  186.         prop y : 0
  187.     end
  188.     
  189.     x of myPoint 
  190.     --> 0
  191.  
  192. Here, myPoint is not reset, but the definition of Point is updated to contain
  193. the new coordinates (namely the new value of x).  Named actors gives a
  194. convenient way of always finding an actor, no matter what context you're in.
  195. Instances can be created by copying an actor:
  196.  
  197.     copy actor "Point" to pt1
  198.     set x of pt1 to 33
  199.     
  200.     copy actor "Point" to pt2
  201.     set x of pt2 to 44
  202.  
  203. Here, we've made two instances of the "class" Point, pt1 and pt2, and then set
  204. each of their x coordinates.  We can tell they're different points by getting
  205. their x coordinates back out, and seeing that they're different:    
  206.  
  207.     x of pt1
  208.     --> 33
  209.     
  210.     x of pt2
  211.     --> 44
  212.  
  213. Note that we have to use copy to do this.  If we use set (i.e. 'set pt1 to
  214. actor "Point"') we would have just made our local variable pt1 a reference to
  215. actor "Point", we wouldn't have actually made a copy.
  216.  
  217. The other way to use actors as classes is to write constructor functions.  These
  218. are functions (procedures, handlers, subroutines, whatever you want to call 
  219. them) that initialize and return actors when they are run.  For example:
  220.  
  221.     on makePoint()
  222.         actor
  223.             prop x : 0
  224.             prop y : 0
  225.         end
  226.     end
  227.  
  228. This defines a function called makePoint that takes no arguments and returns
  229. and actor with an x and y property.  This may look a little strange at first
  230. (perhaps a little too simple), but here's how it works.  When you call this 
  231. function, it always makes a new actor (remember actor definitions construct
  232. actors, so a new one gets constructed whenever you call this function).  Then
  233. the last result (the new actor) is returned from the function when it 
  234. terminates.  (We could have inserted a 'return result' statement after the 
  235. actor definition, but it's not necessary.)
  236.  
  237. Now we can use this constructor function just like we used the named actor
  238. "Point", above:
  239.  
  240.     set pt1 to makePoint()
  241.     set x of pt1 to 33
  242.     
  243.     set pt2 to makePoint()
  244.     set x of pt2 to 44
  245.  
  246. Note that we don't have to use copy here, because calling the function always
  247. makes a new actor.  Calling copy would just copy that new actor again, 
  248. needlessly.  We can fetch the individual values of the x coordinates just as
  249. before:
  250.  
  251.     x of pt1
  252.     --> 33
  253.     
  254.     x of pt2
  255.     --> 44
  256.  
  257. So why go to all this hassle of making constructor functions?  Because this 
  258. allows us to have parameterized classes.  Parameterized classes allow us to 
  259. make instances with different initial values to properties:
  260.  
  261.     on makePoint(initialX, initialY)
  262.         actor
  263.             prop x : initialX
  264.             prop y : initialY
  265.         end
  266.     end
  267.  
  268. We can now construct our two points as:
  269.  
  270.     set pt1 to makePoint(33, 0)
  271.     
  272.     set pt2 to makePoint(44, 0)
  273.  
  274. and again retrieve their individual x (and y) coordinates as before.
  275.  
  276.  
  277. Inheritance
  278. -----------
  279. Actors support "delegation"-style inheritance.  Delegation means that actors
  280. can forward messages on to a parent actor who may choose to handle the message.
  281. Actors currently only support single inheritance -- a single parent actor.
  282.  
  283. The use of delegation-style inheritance works well with our use of actors as
  284. both classes and instances.  Actors being used as classes can "inherit" from
  285. other classes also being used as classes.  Actors being used as instances can
  286. "delegate" a message to another actor being used as an instance.  These are
  287. essentially the same concept.  Actors simply forward messages on to their
  288. "parent" when they don't handle the message themselves.
  289.  
  290. Let's begin by showing an example of inheritance.  Suppose we define a Stack
  291. class that we'll use as a base class to inherit from:
  292.  
  293.     actor "Stack"
  294.         prop elements : {}
  295.         
  296.         on push(x)
  297.             set elements to {x} & elements
  298.             return x
  299.         end
  300.         
  301.         on pop()
  302.             set value to top()
  303.             set elements to rest of elements
  304.             return value
  305.         end
  306.         
  307.         on top()
  308.             return item 1 of elements
  309.         end
  310.     end
  311.  
  312. This class uses a property, elements, which is a list containing the elements 
  313. (we could have used a vector and an index counter, but this will do for now).
  314.  
  315. Now we can define a subclass by simply mentioning actor "Stack" as the parent
  316. property of the subclass.  We can also add a new method, dup, which duplicates
  317. the top of the stack:
  318.  
  319.     actor "SuperStack"
  320.     
  321.         prop parent : actor "Stack"
  322.         
  323.         on dup()
  324.             push(top())
  325.         end
  326.     end
  327.  
  328. Here, SuperStack doesn't define a push or top method, but inherits them from 
  329. Stack.  When it is sent the dup message, it sends itself the push and top
  330. messages which end up being handled by Stack.
  331.  
  332. Now let's use instances of our SuperStack to see how it works:
  333.  
  334.     copy actor "SuperStack" to s1
  335.     copy actor "SuperStack" to s2
  336.     
  337.     tell s1 to push(3)
  338.     tell s2 to push(4)
  339.     tell s1 to dup()
  340.     
  341.     tell s2 to pop()
  342.     --> 4
  343.     
  344.     tell s1
  345.         {pop(), pop()}
  346.     end
  347.     --> {3, 3}
  348.  
  349. The trick that makes this work is that when SuperStack is copied, its parent,
  350. Stack, is also copied.  This gives each new instance new copies of all the
  351. properties.  S1 and s2 each get their own copy of the elements property.
  352.  
  353. Rather than just inheriting methods, we can also override methods -- replace
  354. them with methods that do something completely different.  We can make our
  355. SuperStack not allow the pop method by giving it a new pop method that 
  356. signals an error:
  357.  
  358.     actor "SuperStack"
  359.     
  360.         prop parent : actor "Stack"
  361.         
  362.         on dup()
  363.             push(top())
  364.         end
  365.         
  366.         on pop()
  367.             error "SuperStack doesn't allow pop"
  368.         end
  369.     end
  370.  
  371. Now we can perform push, top, and dup on instances of SuperStacks, but not pop:
  372.  
  373.     copy actor "SuperStack" to s3
  374.     
  375.     tell s3 to push(6)
  376.     tell s3 to dup()
  377.     tell s3 to pop()
  378.     --> Error: SuperStack doesn't allow pop
  379.  
  380.  
  381. Writing "Wrapper" methods
  382. -------------------------
  383. Sometimes when doing OOP it is necessary to not just inherit or override 
  384. methods, but to augment their functionality.  This can be done by using the
  385. "continue" statement.
  386.  
  387. Let's write a CountingStack class which augments the functionality of push and
  388. pop to keep a count of the number of elements currently on the stack:
  389.  
  390.     actor "CountingStack"
  391.     
  392.         prop parent : actor "Stack"
  393.         
  394.         prop numberOfElements : 0
  395.         
  396.         on push(x)
  397.             set numberOfElements to numberOfElements + 1
  398.             continue push(x)
  399.         end
  400.         
  401.         on pop()
  402.             set numberOfElements to numberOfElements - 1
  403.             continue pop()
  404.         end
  405.     end
  406.  
  407. Here, continue causes the method to be handed off to the parent, in this case
  408. the Stack class.  Here's an example of it in action:
  409.  
  410.     copy actor "CountingStack" to cs1
  411.     
  412.     tell cs1
  413.         push(33)
  414.         push(44)
  415.         push(55)
  416.     end
  417.     
  418.     numberOfElements of cs1
  419.     --> 3
  420.     
  421.     tell cs1
  422.         pop()
  423.         get its numberOfElements
  424.     end
  425.     --> 2
  426.  
  427. When continue is called, the arguments passed to the parent may be changed.
  428. Similarly, any result returned from the parent's method will be returned from
  429. the continue statement.  Just to demonstrate this capability, can make a 
  430. subclass of Stack that (assuming the items pushed are integers) stores them
  431. internally values as multiplied by 10.  Then when popped off the stack, it
  432. divides them back down to the original value:
  433.  
  434.     actor "PerverseStack"
  435.     
  436.         prop parent : actor "Stack"
  437.         
  438.         on push(x)
  439.             continue push(x * 10)
  440.         end
  441.         
  442.         on pop()
  443.             continue pop()
  444.             return result div 10
  445.         end
  446.     end
  447.  
  448.  
  449.     copy actor "PerverseStack" to ps
  450.     
  451.     tell ps
  452.         push(3)
  453.         push(4)
  454.         return {pop(), pop()}
  455.     end
  456.     -> {4, 3}
  457.  
  458. But really, this is useful.
  459.